﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.Geometries;
using YJKGridTopologyCal.Index.KdTree;
using YJKGridTopologyCal.Triangulate.QuadEdge;

namespace YJKGridTopologyCal.Triangulate
{
	public class ConformingDelaunayTriangulator
	{
		private static Envelope ComputeVertexEnvelope(IEnumerable<Vertex> vertices)
		{
			Envelope envelope = new Envelope();
			foreach (Vertex vertex in vertices)
			{
				envelope.ExpandToInclude(vertex.Coordinate);
			}
			return envelope;
		}

		public ConformingDelaunayTriangulator(IEnumerable<Vertex> initialVertices, double tolerance)
		{
			this._initialVertices = new List<Vertex>(initialVertices);
			this._tolerance = tolerance;
			this._kdt = new KdTree<Vertex>(tolerance);
		}

		public void SetConstraints(IList<Segment> segments, IList<Vertex> segVertices)
		{
			this._segments = segments;
			this._segVertices = segVertices;
		}

		public IConstraintSplitPointFinder SplitPointFinder
		{
			get
			{
				return this._splitFinder;
			}
			set
			{
				this._splitFinder = value;
			}
		}

		public double Tolerance
		{
			get
			{
				return this._tolerance;
			}
		}

		public ConstraintVertexFactory VertexFactory
		{
			get
			{
				return this._vertexFactory;
			}
			set
			{
				this._vertexFactory = value;
			}
		}

		public QuadEdgeSubdivision Subdivision
		{
			get
			{
				return this._subdiv;
			}
		}

		public KdTree<Vertex> KDT
		{
			get
			{
				return this._kdt;
			}
		}

		public IList<Vertex> InitialVertices
		{
			get
			{
				return this._initialVertices;
			}
		}

		public ICollection<Segment> ConstraintSegments
		{
			get
			{
				return this._segments;
			}
		}

		public IGeometry ConvexHull
		{
			get
			{
				return this._convexHull;
			}
		}

		private void ComputeBoundingBox()
		{
			Envelope env = ConformingDelaunayTriangulator.ComputeVertexEnvelope(this._initialVertices);
			Envelope other = ConformingDelaunayTriangulator.ComputeVertexEnvelope(this._segVertices);
			Envelope envelope = new Envelope(env);
			envelope.ExpandToInclude(other);
			double val = envelope.Width * 0.2;
			double val2 = envelope.Height * 0.2;
			double distance = Math.Max(val, val2);
			this._computeAreaEnv = new Envelope(envelope);
			this._computeAreaEnv.ExpandBy(distance);
		}

		private void ComputeConvexHull()
		{
			GeometryFactory geomFactory = new GeometryFactory();
			ConvexHull convexHull = new ConvexHull(this.GetPointArray(), geomFactory);
			this._convexHull = convexHull.GetConvexHull();
		}

		private Coordinate[] GetPointArray()
		{
			Coordinate[] array = new Coordinate[this._initialVertices.Count + this._segVertices.Count];
			int num = 0;
			foreach (Vertex vertex in this._initialVertices)
			{
				array[num++] = vertex.Coordinate;
			}
			foreach (Vertex vertex2 in this._segVertices)
			{
				array[num++] = vertex2.Coordinate;
			}
			return array;
		}

		private ConstraintVertex CreateVertex(Coordinate p)
		{
			ConstraintVertex result;
			if (this._vertexFactory != null)
			{
				result = this._vertexFactory.CreateVertex(p, null);
			}
			else
			{
				result = new ConstraintVertex(p);
			}
			return result;
		}

		private ConstraintVertex CreateVertex(Coordinate p, Segment seg)
		{
			ConstraintVertex constraintVertex;
			if (this._vertexFactory != null)
			{
				constraintVertex = this._vertexFactory.CreateVertex(p, seg);
			}
			else
			{
				constraintVertex = new ConstraintVertex(p);
			}
			constraintVertex.IsOnConstraint = true;
			return constraintVertex;
		}

		private void InsertSites(ICollection<Vertex> vertices)
		{
			foreach (Vertex vertex in vertices)
			{
				this.InsertSite((ConstraintVertex)vertex);
			}
		}

		private ConstraintVertex InsertSite(ConstraintVertex v)
		{
			KdNode<Vertex> kdNode = this._kdt.Insert(v.Coordinate, v);
			if (!kdNode.IsRepeated)
			{
				this._incDel.InsertSite(v);
				return v;
			}
			ConstraintVertex constraintVertex = (ConstraintVertex)kdNode.Data;
			constraintVertex.Merge(v);
			return constraintVertex;
		}

		public void InsertSite(Coordinate p)
		{
			this.InsertSite(this.CreateVertex(p));
		}

		public void FormInitialDelaunay()
		{
			this.ComputeBoundingBox();
			this._subdiv = new QuadEdgeSubdivision(this._computeAreaEnv, this._tolerance);
			this._subdiv.SetLocator(new LastFoundQuadEdgeLocator(this._subdiv));
			this._incDel = new IncrementalDelaunayTriangulator(this._subdiv);
			this.InsertSites(this._initialVertices);
		}

		public void EnforceConstraints()
		{
			this.AddConstraintVertices();
			int num = 0;
			int num2;
			do
			{
				num2 = this.EnforceGabriel(this._segments);
				num++;
			}
			while (num2 > 0 && num < 99);
			if (num == 99 && !Debugger.IsAttached)
			{
				throw new ConstraintEnforcementException("Too many splitting iterations while enforcing constraints.  Last split point was at: ", this._splitPt);
			}
		}

		private void AddConstraintVertices()
		{
			this.ComputeConvexHull();
			this.InsertSites(this._segVertices);
		}

		private int EnforceGabriel(ICollection<Segment> segsToInsert)
		{
			List<Segment> list = new List<Segment>();
			int num = 0;
			List<Segment> list2 = new List<Segment>();
			foreach (Segment segment in segsToInsert)
			{
				Coordinate coordinate = this.FindNonGabrielPoint(segment);
				if (coordinate != null)
				{
					this._splitPt = this._splitFinder.FindSplitPoint(segment, coordinate);
					ConstraintVertex constraintVertex = this.CreateVertex(this._splitPt, segment);
					this.InsertSite(constraintVertex).Coordinate.Equals2D(this._splitPt);
					Segment item = new Segment(segment.StartX, segment.StartY, segment.StartZ, constraintVertex.X, constraintVertex.Y, constraintVertex.Z, segment.Data);
					Segment item2 = new Segment(constraintVertex.X, constraintVertex.Y, constraintVertex.Z, segment.EndX, segment.EndY, segment.EndZ, segment.Data);
					list.Add(item);
					list.Add(item2);
					list2.Add(segment);
					num++;
				}
			}
			foreach (Segment item3 in list2)
			{
				segsToInsert.Remove(item3);
			}
			foreach (Segment item4 in list)
			{
				segsToInsert.Add(item4);
			}
			return num;
		}

		private Coordinate FindNonGabrielPoint(Segment seg)
		{
			Coordinate start = seg.Start;
			Coordinate end = seg.End;
			Coordinate coordinate = new Coordinate((start.X + end.X) / 2.0, (start.Y + end.Y) / 2.0);
			double num = start.Distance(coordinate);
			Envelope envelope = new Envelope(coordinate);
			envelope.ExpandBy(num);
			IEnumerable<KdNode<Vertex>> enumerable = this._kdt.Query(envelope);
			Coordinate coordinate2 = null;
			double num2 = double.MaxValue;
			foreach (KdNode<Vertex> kdNode in enumerable)
			{
				Coordinate coordinate3 = kdNode.Coordinate;
				if (!coordinate3.Equals2D(start) && !coordinate3.Equals2D(end))
				{
					double num3 = coordinate.Distance(coordinate3);
					if (num3 < num)
					{
						double num4 = num3;
						if (coordinate2 == null || num4 < num2)
						{
							coordinate2 = coordinate3;
							num2 = num4;
						}
					}
				}
			}
			return coordinate2;
		}

		private readonly IList<Vertex> _initialVertices;

		private IList<Vertex> _segVertices;

		private IList<Segment> _segments = new List<Segment>();

		private QuadEdgeSubdivision _subdiv;

		private IncrementalDelaunayTriangulator _incDel;

		private IGeometry _convexHull;

		private IConstraintSplitPointFinder _splitFinder = new NonEncroachingSplitPointFinder();

		private readonly KdTree<Vertex> _kdt;

		private ConstraintVertexFactory _vertexFactory;

		private Envelope _computeAreaEnv;

		private Coordinate _splitPt;

		private readonly double _tolerance;

		private const int MaxSplitIteration = 99;
	}
}
